#include <swap.h>
#include <swapfs.h>
#include <swap_fifo.h>
#include <stdio.h>
#include <string.h>
#include <memlayout.h>
#include <pmm.h>
#include <mmu.h>

// the valid vaddr for check is between 0~CHECK_VALID_VADDR-1
#define CHECK_VALID_VIR_PAGE_NUM 5
#define BEING_CHECK_VALID_VADDR 0X1000
#define CHECK_VALID_VADDR (CHECK_VALID_VIR_PAGE_NUM+1)*0x1000
// the max number of valid physical page for check
#define CHECK_VALID_PHY_PAGE_NUM 4
// the max access seq number
#define MAX_SEQ_NO 10

static struct swap_manager *sm;
size_t max_swap_offset;

volatile int swap_init_ok = 0;

unsigned int swap_page[CHECK_VALID_VIR_PAGE_NUM];

unsigned int swap_in_seq_no[MAX_SEQ_NO],swap_out_seq_no[MAX_SEQ_NO];

static void check_swap(void);

int
swap_init(void)
{
     // 初始化交换文件系统
     swapfs_init();

     // 检查最大交换偏移量是否在合法范围内
     if (!(1024 <= max_swap_offset && max_swap_offset < MAX_SWAP_OFFSET_LIMIT))
     {    
           // 如果最大交换偏移量不合法，输出错误信息并panic
          panic("bad max_swap_offset %08x.\n", max_swap_offset);
     }
     
     // 选择并设置交换管理器为FIFO（先进先出）策略
     sm = &swap_manager_fifo;

     // 调用选定交换管理器的初始化函数
     int r = sm->init();

     // 如果交换管理器初始化成功
     if (r == 0)
     {
          // 标记交换空间初始化为成功
          swap_init_ok = 1;

          cprintf("SWAP: manager = %s\n", sm->name);
          // 检查交换空间状态
          check_swap();
     }
     // 返回初始化结果
     return r;
}

int
swap_init_mm(struct mm_struct *mm)
{
     //调用 sm 结构体中的 init_mm 函数，并将 mm 作为参数传递
     return sm->init_mm(mm);
}

/**
 * swap_tick_event - 触发与交换相关的tick事件
 * @mm: 进程的内存描述符
 *
 * 此函数通过调用sm结构体中的tick_event函数来处理与交换相关的tick事件。
 * 它将进程的内存描述符作为参数传递给sm的tick_event函数，并返回该调用的结果。
 *
 * 返回值: sm->tick_event(mm)的返回值
 */
int
swap_tick_event(struct mm_struct *mm)
{
     return sm->tick_event(mm);
}

/**
 * 调用结构体sm中的map_swappable函数来处理页面交换操作
 * 
 * @param mm 内存描述符，表示一个内存管理对象
 * @param addr 需要交换的内存地址
 * @param page 指向页面的指针，表示要交换出或交换入的页面
 * @param swap_in 表示交换方向的标志，非零表示交换入，零表示交换出
 * @return 返回map_swappable函数的执行结果，通常表示操作的成功与否
 *
 * 此函数的作用是作为交换操作的接口，根据swap_in参数决定是将页面交换入还是交换出
 * 它实际上调用了结构体sm中定义的map_swappable函数，因此具体的操作逻辑依赖于该函数的实现
 */
int
swap_map_swappable(struct mm_struct *mm, uintptr_t addr, struct Page *page, int swap_in)
{
     return sm->map_swappable(mm, addr, page, swap_in);
}

/**
 * 将指定内存区域标记为不可交换到磁盘
 * 
 * @param mm 包含待操作内存区域的内存描述符
 * @param addr 待操作的内存区域的地址
 * @return 返回操作结果，0表示成功，非零表示失败
 *
 * 此函数通过调用sm->set_unswappable方法来实现，其主要目的是防止指定的内存区域被交换到磁盘，
 * 通常用于那些需要常驻内存不能被交换出的特殊页面。成功时返回0，失败时返回非零值。
 */
int
swap_set_unswappable(struct mm_struct *mm, uintptr_t addr)
{
     return sm->set_unswappable(mm, addr);
}

volatile unsigned int swap_out_num=0;

/**
 * 从内存中交换出指定数量的页面到磁盘交换空间。
 * 
 * @param mm 内存描述符，表示要执行交换出操作的内存空间。
 * @param n 需要交换出的页面数量。
 * @param in_tick 当前的时钟周期，用于判断页面是否已经过期。
 * 
 * @return 实际交换出的页面数量。
 * 
 * 此函数通过选择 victim 页面并将其内容写入到交换空间中，来实现内存中页面的交换出操作。
 * 它主要用于内存压力较大时，释放内存空间，确保系统稳定运行。
 */
int
swap_out(struct mm_struct *mm, int n, int in_tick)
{
     int i;
     for (i = 0; i != n; ++ i)// 循环 n 次，每次处理一个页面
     {
          uintptr_t v; // 用于存储页面的虚拟地址
          //struct Page **ptr_page=NULL;
          struct Page *page;  // 用于存储选中的页面
          // cprintf("i %d, SWAP: call swap_out_victim\n",i);
          // 调用 swap_out_victim 函数选择一个 victim 页面
          int r = sm->swap_out_victim(mm, &page, in_tick);
          if (r != 0) {// 如果选择失败
                    cprintf("i %d, swap_out: call swap_out_victim failed\n",i);// 输出错误信息
                  break;// 中断循环
          }          
          //assert(!PageReserved(page));

          //cprintf("SWAP: choose victim page 0x%08x\n", page);
          
          v=page->pra_vaddr;  // 获取页面的虚拟地址
          pte_t *ptep = get_pte(mm->pgdir, v, 0); // 获取页面表项指针
          assert((*ptep & PTE_P) != 0); // 断言页面在物理内存中存在

          if (swapfs_write( (page->pra_vaddr/PGSIZE+1)<<8, page) != 0) {// 将页面内容写入交换文件
                    cprintf("SWAP: failed to save\n");// 如果写入失败，输出错误信息
                    sm->map_swappable(mm, v, page, 0);// 标记页面为不可交换
                    continue;
          }
          else {
                    cprintf("swap_out: i %d, store page in vaddr 0x%x to disk swap entry %d\n", i, v, page->pra_vaddr/PGSIZE+1);
                    *ptep = (page->pra_vaddr/PGSIZE+1)<<8;  // 更新页面表项，标记页面已交换到磁盘
                    free_page(page);    // 释放页面
          }
          
          tlb_invalidate(mm->pgdir, v); // 使 TLB 中对应的条目失效
     }
     return i; // 返回实际交换出的页面数量
}

//实现一个页交换功能。
int
swap_in(struct mm_struct *mm, uintptr_t addr, struct Page **ptr_result)
{
     //分配一个新的页面result
     struct Page *result = alloc_page();
     assert(result!=NULL);
     //获取虚拟地址 addr 对应的页表项指针 ptep
     pte_t *ptep = get_pte(mm->pgdir, addr, 0);
     // cprintf("SWAP: load ptep %x swap entry %d to vaddr 0x%08x, page %x, No %d\n", ptep, (*ptep)>>8, addr, result, (result-pages));
    
     int r;
     //从交换文件中读取数据到新分配的页面 result 中
     if ((r = swapfs_read((*ptep), result)) != 0)
     {
        assert(r!=0);
     }
     cprintf("swap_in: load disk swap entry %d with swap_page in vadr 0x%x\n", (*ptep)>>8, addr);
     *ptr_result=result;
     return 0;
}


/**
 * check_content_set函数用于验证页面故障处理的正确性。
 * 通过向特定内存地址写入数据，并检查页面故障次数是否符合预期，来验证页面管理机制的正确性。
 * 此函数没有输入参数和返回值。
 */
static inline void
check_content_set(void)
{
     *(unsigned char *)0x1000 = 0x0a;
     assert(pgfault_num==1);
     *(unsigned char *)0x1010 = 0x0a;
     assert(pgfault_num==1);
     *(unsigned char *)0x2000 = 0x0b;
     assert(pgfault_num==2);
     *(unsigned char *)0x2010 = 0x0b;
     assert(pgfault_num==2);
     *(unsigned char *)0x3000 = 0x0c;
     assert(pgfault_num==3);
     *(unsigned char *)0x3010 = 0x0c;
     assert(pgfault_num==3);
     *(unsigned char *)0x4000 = 0x0d;
     assert(pgfault_num==4);
     *(unsigned char *)0x4010 = 0x0d;
     assert(pgfault_num==4);
}

/**
 * 检查内容访问权限
 * 
 * 此函数旨在通过调用swap管理器的检查方法来确定当前上下文是否具有访问特定内容的权限
 * 它提供了一种快速、内联的方式，以确保在进行内容访问之前，所有的权限都已经被正确地授予
 * 
 * @return int 返回权限检查的结果，非零表示成功，零表示失败
 */
static inline int
check_content_access(void)
{
    // 调用swap管理器的检查方法，并将结果返回
    int ret = sm->check_swap();
    return ret;
}

struct Page * check_rp[CHECK_VALID_PHY_PAGE_NUM];
pte_t * check_ptep[CHECK_VALID_PHY_PAGE_NUM];
unsigned int check_swap_addr[CHECK_VALID_VIR_PAGE_NUM];

extern free_area_t free_area;

#define free_list (free_area.free_list)
#define nr_free (free_area.nr_free)

// 检查交换机制的正确性，通过模拟页面替换算法
static void
check_swap(void)
{
    //backup mem env// 备份内存环境，确保检查后没有页面丢失
     int ret, count = 0, total = 0, i;
     list_entry_t *le = &free_list;
     while ((le = list_next(le)) != &free_list) {
        struct Page *p = le2page(le, page_link);// 将链表条目转换为页面结构
        assert(PageProperty(p));// 断言页面属性有效
        count ++, total += p->property;// 统计页面数量和属性总和
     }
     assert(total == nr_free_pages());// 断言统计的属性总和与空闲页面数一致
     cprintf("BEGIN check_swap: count %d, total %d\n",count,total);// 打印初始状态
     
     //now we set the phy pages env // 设置物理页面环境    
     struct mm_struct *mm = mm_create();// 创建内存管理结构
     assert(mm != NULL); // 断言内存管理结构创建成功

     extern struct mm_struct *check_mm_struct;// 声明外部变量
     assert(check_mm_struct == NULL);// 断言外部变量为空

     // 将新创建的内存管理结构赋值给外部变量
     check_mm_struct = mm;

     pde_t *pgdir = mm->pgdir = boot_pgdir;// 设置页目录
     assert(pgdir[0] == 0);// 断言页目录的第一个条目为空

     // 创建虚拟内存区域
     struct vma_struct *vma = vma_create(BEING_CHECK_VALID_VADDR, CHECK_VALID_VADDR, VM_WRITE | VM_READ);
     assert(vma != NULL);// 断言虚拟内存区域创建成功

     // 插入虚拟内存区域到内存管理结构
     insert_vma_struct(mm, vma);

     //setup the temp Page Table vaddr 0~4MB/ 设置临时页表，用于虚拟地址 0~4MB
     cprintf("setup Page Table for vaddr 0X1000, so alloc a page\n");// 打印设置页表的信息
     pte_t *temp_ptep=NULL;
     temp_ptep = get_pte(mm->pgdir, BEING_CHECK_VALID_VADDR, 1);// 获取页表项
     assert(temp_ptep!= NULL);// 断言获取页表项成功
     cprintf("setup Page Table vaddr 0~4MB OVER!\n");// 打印设置页表完成的信息
     
     for (i=0;i<CHECK_VALID_PHY_PAGE_NUM;i++) {
          check_rp[i] = alloc_page();// 分配页面
          assert(check_rp[i] != NULL );// 断言分配页面成功
          assert(!PageProperty(check_rp[i]));// 断言页面属性无效
     }
     list_entry_t free_list_store = free_list;// 保存当前空闲列表
     list_init(&free_list);// 初始化空闲列表
     assert(list_empty(&free_list));// 断言空闲列表为空
     
     //assert(alloc_page() == NULL);
     
     unsigned int nr_free_store = nr_free;// 保存当前空闲页面数
     nr_free = 0;// 将空闲页面数设为 0
     for (i=0;i<CHECK_VALID_PHY_PAGE_NUM;i++) {
        free_pages(check_rp[i],1);// 释放页面
     }
     assert(nr_free==CHECK_VALID_PHY_PAGE_NUM);// 断言释放的页面数正确
     
     cprintf("set up init env for check_swap begin!\n");// 打印设置初始环境开始的信息
     //setup initial vir_page<->phy_page environment for page relpacement algorithm 
     // 设置初始虚拟到物理页面环境，用于页面替换算法
     
     pgfault_num=0;// 初始化页面故障数
     
     check_content_set();// 设置检查内容
     assert( nr_free == 0);  // 断言空闲页面数为 0       
     for(i = 0; i<MAX_SEQ_NO ; i++) 
         swap_out_seq_no[i]=swap_in_seq_no[i]=-1;// 初始化页面替换序列号
     
     for (i= 0;i<CHECK_VALID_PHY_PAGE_NUM;i++) {
         check_ptep[i]=0;
         check_ptep[i] = get_pte(pgdir, (i+1)*0x1000, 0);// 获取页表项
         //cprintf("i %d, check_ptep addr %x, value %x\n", i, check_ptep[i], *check_ptep[i]);
         assert(check_ptep[i] != NULL);// 断言获取页表项成功
         assert(pte2page(*check_ptep[i]) == check_rp[i]); // 断言页表项对应的页面正确
         assert((*check_ptep[i] & PTE_P));  // 断言页表项有效        
     }
     cprintf("set up init env for check_swap over!\n");// 打印设置初始环境完成的信息
     // now access the virt pages to test  page relpacement algorithm 
     ret=check_content_access();
     assert(ret==0); // 断言访问检查成功
     
     //restore kernel mem env
     for (i=0;i<CHECK_VALID_PHY_PAGE_NUM;i++) {
         free_pages(check_rp[i],1);
     } 

     //free_page(pte2page(*temp_ptep));
     
     mm_destroy(mm);// 销毁内存管理结构
         
     nr_free = nr_free_store;// 恢复空闲页面数
     free_list = free_list_store;// 恢复空闲列表

     
     le = &free_list;
     while ((le = list_next(le)) != &free_list) {
         struct Page *p = le2page(le, page_link);// 将链表条目转换为页面结构
         count --, total -= p->property;// 更新页面数量和属性总和
     }
     cprintf("count is %d, total is %d\n",count,total);// 打印恢复后的状态
     //assert(count == 0);
     
     cprintf("check_swap() succeeded!\n");// 打印检查成功的信息
}
